/****

    activequant - activestocks.eu

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

	
	contact  : contact@activestocks.eu
    homepage : http://www.activestocks.eu

 ****/
package org.activequant.core.domainmodel.data;

import static org.activequant.util.tools.IdentityUtils.equalsTo;
import static org.activequant.util.tools.IdentityUtils.safeCompare;
import static org.activequant.util.tools.IdentityUtils.safeHashCode;

import org.activequant.core.domainmodel.InstrumentSpecification;
import org.activequant.core.domainmodel.SeriesSpecification;
import org.activequant.core.types.TimeFrame;
import org.activequant.core.types.TimeStamp;
import org.activequant.util.tools.ArrayUtils;

/**
 * Represents an ohlcv candle. This candle contains inner series and quotes,
 * too.<br>
 * 
 * <em>IMPORTANT</em>: This object has "value" identity. It means that its
 * identity (hashCode, equals, and comapreTo) is determined by subset of the
 * fields, declared "business keys". Other fields are considered a "payload".
 * This identity reflects the suggested unique key constraints in the related
 * sql table.<br>
 * <em>IMPORTANT</em>: If this object is added to a collection that relies on
 * identity, changing business key will break the collection contract and will
 * cause unexpected results.<br>
 * <em>IMPORTANT</em> {@link #getTimeStamp() timeStamp} property corresponds to
 * the Candle's <em>close</em> time.
 * 
 * <br>
 * Business keys for Candle are:
 * <ul>
 * <li>
 * <code>{@link #getInstrumentSpecification() instrumentSpecification}</code>
 * <li><code>{@link #getTimeStamp() timeStamp}</code>
 * <li><code>{@link #getTimeFrame() timeFrame}</code>
 * <li><code>{@link #getOpenPrice() openPrice}</code>
 * <li><code>{@link #getHighPrice() highPrice}</code>
 * <li><code>{@link #getLowPrice() lowPrice}</code>
 * <li><code>{@link #getVolume() volume}</code>
 * <li><code>{@link #getHighTimeStamp() highTimeStamp}</code>
 * <li><code>{@link #getLowTimeStamp() lowTimeStamp}</code>
 * </ul>
 * A MarketDataEntity subclass. Holds the following associated variables:
 * <ul>
 * <li>openPrice(double)</li>
 * <li>highPrice(double)</li>
 * <li>lowPrice(double)</li>
 * <li>closePrice(double)</li>
 * <li>volume(double)</li>
 * <li>timeFrame(TimeFrame)</li>
 * <li>highStamp(TimeStamp)</li>
 * <li>lowStamp(TimeStamp)</li>
 * <li>innerTicks(TradeIndicationSeries)</li>
 * <li>innerCandles(TimeSeries&lt;Candle&gt;)</li>
 * <li>count(int)</li>
 * <li>WAP(double)</li>
 * <li>hasGaps(boolean)</li>
 * </ul>
 * And the following inherited variables:
 * <ul>
 * <li>id(Long)</li>
 * <li>instrumentSpecification(InstrumentSpecification)</li>
 * <li>timeStamp(TimeStamp)</li>
 * </ul>
 * <br>
 * <b>History:</b><br>
 * - [04.05.2007] Created (ustaudinger)<br>
 * - [29.05.2007] Adding inner time series (ustaudinger)<br>
 * - [03.06.2007] Initializing the OHLCV values with -1 (ustaudinger)<br>
 * - [23.06.2007] Moved to new domain model (Erik Nijkamp)<br>
 * - [30.09.2007] Added equals (Erik Nijkamp)<br>
 * - [07.10.2007] Added proper object identity code (hashcode, equals) (Erik
 * Nijkamp)<br>
 * - [08.10.2007] Aligned identity with comparable (Mike Kroutikov)<br>
 * - [05.11.2007] Fixed cloning (Erik Nijkamp)<br>
 * 
 * @author Ulrich Staudinger
 * @author Erik Nijkamp
 */
public class Candle extends MarketDataEntity<Candle> {
	private static final long serialVersionUID = -8600374819004303005L;
	/**
	 * private double openPrice = NOT_SET;
	 */
	private double openPrice = NOT_SET;
	/**
	 * private double highPrice = NOT_SET;
	 */
	private double highPrice = NOT_SET;
	/**
	 * private double lowPrice = NOT_SET;
	 */
	private double lowPrice = NOT_SET;
	/**
	 * private double closePrice = NOT_SET;
	 */
	private double closePrice = NOT_SET;
	/**
	 * private double volume = 0.0;
	 */
	private double volume = 0.0;
	/**
	 * private TimeFrame timeFrame = null;
	 */
	private TimeFrame timeFrame = null;
	/**
	 * private TimeStamp highStamp = null;<br/>
	 * initialized with date. Contains the time of the high.
	 */
	private TimeStamp highStamp = null;

	/**
	 * private TimeStamp lowStamp = null;<br/>
	 * initialized with date. Contains the time of the low.
	 */
	private TimeStamp lowStamp = null;

	/**
	 * private TradeIndicationSeries innerTicks;<br/>
	 * price indications out of which this candle was built.
	 */
	private TradeIndicationSeries innerTicks;

	/**
	 * private TimeSeries&lt;Candle&gt; innerCandles;<br/>
	 * inner candle series
	 */
	private TimeSeries<Candle> innerCandles;
	/**
	 * private int count = 0;<br/>
	 * IB specific: when TRADES historical data is returned, represents the number of trades that occurred during the time period the candle covers
	 */
	private int count = 0;
	/**
	 * private double WAP=0;<br/>
	 * IB specific: The weighted average price during the time covered by the candle.
	 */
	 private double WAP=0;
	 /**
	  * boolean hasGaps=false;<br/>
	  * IB specific: Whether or not there are gaps in the data.
	  */
	 boolean hasGaps=false;
	/**
	 * empty Candle constructor
	 */
	public Candle() {

	}
	/**
	 * constructs a Candle(MarketDataEntity subclass) and sets its associated/inherited instrumentSpecification(InstrumentSpecification) with the given spec(InstrumentSpecification)
	 * @param spec
	 */
	public Candle(InstrumentSpecification spec) {
		setInstrumentSpecification(spec);
	}
	/**
	 * Constructs a Candle(MarketDataEntity subclass) using the given date(TimeStamp), openPrice(double), highPrice(double), lowPrice(double) and closePrice(double)
	 * to set its associated/inherited timeStamp(TimeStamp), openPrice(double), highPrice(double), lowPrice(double) and closePrice(double)
	 * @param date
	 * @param openPrice
	 * @param highPrice
	 * @param lowPrice
	 * @param closePrice
	 */
	public Candle(TimeStamp date, double openPrice, double highPrice, double lowPrice, double closePrice) {
		super(date);
		this.openPrice = openPrice;
		this.highPrice = highPrice;
		this.lowPrice = lowPrice;
		this.closePrice = closePrice;
	}
	/**
	 * Constructs a Candle(MarketDataEntity subclass) using the given date(TimeStamp), openPrice(double), highPrice(double), lowPrice(double), closePrice(double) and volume(double)
	 * to set its associated/inherited timeStamp(TimeStamp), openPrice(double), highPrice(double), lowPrice(double), closePrice(double) and volume(double)
	 * @param date
	 * @param openPrice
	 * @param highPrice
	 * @param lowPrice
	 * @param closePrice
	 * @param volume
	 */
	public Candle(TimeStamp date, double openPrice, double highPrice, double lowPrice, double closePrice, double volume) {
		this(date, openPrice, highPrice, lowPrice, closePrice);
		this.volume = volume;
	}
	/**
	 * Constructs a Candle(MarketDataEntity subclass) using the given spec(InstrumentSpecification), date(TimeStamp), open(double), high(double), low(double) and close(double)
	 * to set its associated/inherited instrumentSpecification(InstrumentSpecification), timeStamp(TimeStamp), openPrice(double), highPrice(double), lowPrice(double) and closePrice(double)
	 * @param spec
	 * @param date
	 * @param open
	 * @param high
	 * @param low
	 * @param close
	 */
	public Candle(InstrumentSpecification spec, TimeStamp date, double open, double high, double low, double close) {
		this(date, open, high, low, close);
		setInstrumentSpecification(spec);
	}
	/**
	 * Constructs a Candle(MarketDataEntity subclass) using the given spec(InstrumentSpecification), date(TimeStamp), open(double), high(double), low(double), close(double) and volume(double)
	 * to set its associated/inherited instrumentSpecification(InstrumentSpecification), timeStamp(TimeStamp), openPrice(double), highPrice(double), lowPrice(double), closePrice(double) and volume(double)
	 * @param spec
	 * @param date
	 * @param open
	 * @param high
	 * @param low
	 * @param close
	 * @param volume
	 */
	public Candle(InstrumentSpecification spec, TimeStamp date, double open, double high, double low, double close, long volume) {
		this(date, open, high, low, close);
		this.volume = volume;
		setInstrumentSpecification(spec);
	}
	/**
	 * Constructs a Candle(MarketDataEntity subclass) using the given spec(InstrumentSpecification), date(TimeStamp), openPrice(double), highPrice(double), lowPrice(double), 
	 * closePrice(double), volume(double) and timeFrame(TimeFrame)
	 * to set its associated/inherited instrumentSpecification(InstrumentSpecification), timeStamp(TimeStamp), openPrice(double), highPrice(double), lowPrice(double), 
	 * closePrice(double), volume(double) and timeFrame(TimeFrame)
	 * @param spec
	 * @param date
	 * @param openPrice
	 * @param highPrice
	 * @param lowPrice
	 * @param closePrice
	 * @param volume
	 * @param timeFrame
	 */
	public Candle(InstrumentSpecification spec, TimeStamp date, double openPrice, double highPrice, double lowPrice, double closePrice, double volume, TimeFrame timeFrame) {
		this(date, openPrice, highPrice, lowPrice, closePrice);
		this.volume = volume;
		this.timeFrame = timeFrame;
		this.setInstrumentSpecification(spec);
	}
	/**
	 * Constructs a Candle(MarketDataEntity subclass) using the given date(TimeStamp) to set its associated/inherited timeStamp(TimeStamp)
	 * @param date
	 */
	public Candle(TimeStamp date) {
		super(date);
	}
	/**
	 * Constructs a Candle(MarketDataEntity subclass) using the given spec(InstrumentSpecification) and date(TimeStamp) to set its associated/inherited instrumentSpecification(InstrumentSpecification) and timeStamp(TimeStamp)
	 * @param spec
	 * @param date
	 */
	public Candle(InstrumentSpecification spec, TimeStamp date) {
		super(spec, date);
	}
	/**
	 * Constructs a Candle(MarketDataEntity subclass) using the given spec(InstrumentSpecification) and date(TimeStamp) to set its associated/inherited instrumentSpecification(InstrumentSpecification) and timeStamp(TimeStamp).
	 * The given ohcl(double...) are used to set some of its variables:<br/>
	 * <strong>openPrice = ohcl[0];</strong><br/>
	 * <strong>highPrice = ohcl[1];</strong><br/>
	 * <strong>lowPrice = ohcl[2];</strong><br/>
	 * <strong>closePrice = ohcl[3];</strong>
	 * @param spec
	 * @param date
	 * @param ohcl
	 */
	public Candle(InstrumentSpecification spec, TimeStamp date, double... ohcl) {
		super(spec, date);
		this.openPrice = ohcl[0];
		this.highPrice = ohcl[1];
		this.lowPrice = ohcl[2];
		this.closePrice = ohcl[3];
	}
	/**
	 * Constructs a Candle(MarketDataEntity subclass) using the given date(TimeStamp) to set its associated/inherited timeStamp(TimeStamp).
	 * The given ohcl(double...) are used to set some of its variables:<br/>
	 * <strong>openPrice = ohcl[0];</strong><br/>
	 * <strong>highPrice = ohcl[1];</strong><br/>
	 * <strong>lowPrice = ohcl[2];</strong><br/>
	 * <strong>closePrice = ohcl[3];</strong>
	 * @param date
	 * @param ohcl
	 */
	public Candle(TimeStamp date, double... ohcl) {
		super(date);
		this.openPrice = ohcl[0];
		this.highPrice = ohcl[1];
		this.lowPrice = ohcl[2];
		this.closePrice = ohcl[3];
	}

	/**
	 * clone this Candle instance.
	 * 
	 * @return cloned object
	 */
	@Override
	public Candle clone() {
		return new Candle(getInstrumentSpecification(), getTimeStamp(), openPrice, highPrice, lowPrice, closePrice, volume, timeFrame);
	}

	/**
	 * return object's state as string.<br/>
	 * returns a String in the form: " D : ${timeStamp} O : ${openPrice} H : ${highPrice} L : ${lowPrice} C : ${closePrice} V : ${volume}"
	 * @return text
	 */
	@Override
	public String toString() {
		//return " D : " + getTimeStamp() + " O : " + openPrice + " H : " + highPrice + " L : " + lowPrice + " C : " + closePrice + " V : " + volume;
		return " D:" + getTimeStamp() + " O:" + openPrice + " H:" + highPrice + " L:" + lowPrice + " C:" + closePrice + " V:" + volume + " T:" + count + " VWAP:" + WAP;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int hashCode() {
		// ATTENTION: keep in sync with compareTo();
		return safeHashCode(this.getInstrumentSpecification()) + safeHashCode(this.getTimeStamp()) + safeHashCode(this.timeFrame) + safeHashCode(this.openPrice) + safeHashCode(this.highPrice) + safeHashCode(this.lowPrice) + safeHashCode(this.closePrice)
				+ safeHashCode(this.volume) + safeHashCode(this.highStamp) + safeHashCode(this.lowStamp);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean equals(Object other) {
		// NOTE: delegates to compareTo()
		return equalsTo(this, other);
	}

	/**
	 * {@inheritDoc}
	 */
	public int compareTo(Candle other) {
		// ATTENTION: keep in sync with hashCode();
		int rc;

		rc = safeCompare(this.getInstrumentSpecification(), other.getInstrumentSpecification());
		if (rc != 0)
			return rc;
		rc = safeCompare(this.getTimeStamp(), other.getTimeStamp());
		if (rc != 0)
			return rc;
		rc = safeCompare(this.timeFrame, other.timeFrame);
		if (rc != 0)
			return rc;
		rc = safeCompare(this.openPrice, other.openPrice);
		if (rc != 0)
			return rc;
		rc = safeCompare(this.highPrice, other.highPrice);
		if (rc != 0)
			return rc;
		rc = safeCompare(this.lowPrice, other.lowPrice);
		if (rc != 0)
			return rc;
		rc = safeCompare(this.closePrice, other.closePrice);
		if (rc != 0)
			return rc;
		rc = safeCompare(this.volume, other.volume);
		if (rc != 0)
			return rc;
		rc = safeCompare(this.highStamp, other.highStamp);
		if (rc != 0)
			return rc;
		rc = safeCompare(this.lowStamp, other.lowStamp);
		if (rc != 0)
			return rc;

		return rc;
	}

	/**
	 * OHLC data unset?<br/>
	 * returns whether the associated openPrice(double), highPrice(double), lowPrice(double) and closePrice(double) are all NOT_SET(-1)
	 * @return
	 */
	public boolean isEmpty() {
		return openPrice == NOT_SET && highPrice == NOT_SET && lowPrice == NOT_SET && closePrice == NOT_SET;
	}

	/**
	 * clear ohlc<br/>
	 * sets the associated openPrice(double), highPrice(double), lowPrice(double) and closePrice(double) to NOT_SET(-1)
	 * @return
	 */
	public void reset() {
		openPrice = highPrice = lowPrice = closePrice = NOT_SET;
	}

	/**
	 * returns the associated closePrice(double)
	 * @return the close
	 */
	public double getClosePrice() {
		return closePrice;
	}

	/**
	 * sets the associated closePrice(double) with the given close(double)
	 * @param close
	 *            the close to set
	 */
	public void setClosePrice(double close) {
		this.closePrice = close;
	}

	/**
	 * returns the associated highPrice(double)
	 * @return the high
	 */
	public double getHighPrice() {
		return highPrice;
	}

	/**
	 * sets the associated highPrice(double) with the given high(double)
	 * @param hi
	 *            the high to set
	 */
	public void setHighPrice(double high) {
		this.highPrice = high;
	}

	/**
	 * returns the associated lowPrice(double)
	 * @return the low
	 */
	public double getLowPrice() {
		return lowPrice;
	}

	/**
	 * sets the associated lowPrice(double) with the given low(double)
	 * @param low
	 *            the low to set
	 */
	public void setLowPrice(double low) {
		this.lowPrice = low;
	}

	/**
	 * returns the associated openPrice(double)
	 * @return the open
	 */
	public double getOpenPrice() {
		return openPrice;
	}

	/**
	 * sets the associated openPrice(double) with the given open(double)
	 * @param open
	 *            the open to set
	 */
	public void setOpenPrice(double open) {
		this.openPrice = open;
	}

	/**
	 * returns the associated volume(double)
	 * @return the volume
	 */
	public double getVolume() {
		return volume;
	}

	/**
	 * sets the associated volume(double) with the given volume(double)
	 * @param volume
	 *            the volume to set
	 */
	public void setVolume(double volume) {
		this.volume = volume;
	}
	/**
	 * returns a double[] containing the associated openPrice(double), highPrice(double), lowPrice(double) and closePrice(double) (in that order)
	 * @return
	 */
	public double[] getDoubles() {
		return new double[] { openPrice, highPrice, lowPrice, closePrice };
	}
	/**
	 * sets the associated openPrice(double), highPrice(double), lowPrice(double) and closePrice(double) with the values in the given values(double[]) (must have 4 elements)
	 * @param values
	 */
	public void setDoubles(double[] values) {
		assert (values.length == 4) : "values.length != 4)";
		setOpenPrice(values[0]);
		setHighPrice(values[1]);
		setLowPrice(values[2]);
		setClosePrice(values[3]);
	}

	/**
	 * returns true if this candle is rising, means open < close.<br/>
	 * returns whether the associated openPrice(double) is less than the closePrice(double)
	 * returns false
	 **/
	public boolean isRising() {
		return openPrice < closePrice;
	}
	/**
	 * returns true if this candle is dropping, means open > close.<br/>
	 * returns whether the associated openPrice(double) is greater than the closePrice(double)
	 * returns false
	 **/
	public boolean isDropping() {
		return openPrice > closePrice;
	}
	/**
	 * returns the associated highStamp(TimeStamp)
	 * @return
	 */
	public TimeStamp getHighTimeStamp() {
		return highStamp;
	}
	/**
	 * sets the associated highStamp(TimeStamp) with the given highDate(TimeStamp)
	 * @param highDate
	 */
	public void setHighTimeStamp(TimeStamp highDate) {
		this.highStamp = highDate;
	}
	/**
	 * returns the associated lowStamp(TimeStamp)
	 * @return
	 */
	public TimeStamp getLowTimeStamp() {
		return lowStamp;
	}
	/**
	 * sets the associated lowStamp(TimeStamp) with the given lowDate(TimeStamp)
	 * @param lowDate
	 */
	public void setLowTimeStamp(TimeStamp lowDate) {
		this.lowStamp = lowDate;
	}
	/**
	 * returns the associated innerTicks(TradeIndicationSeries)
	 * @return
	 */
	public TradeIndicationSeries getInnerTicks() {
		return innerTicks;
	}
	/**
	 * sets the associated innerTicks(TradeIndicationSeries) with the given innerTicks(TradeIndicationSeries)
	 * @param innerTicks
	 */
	public void setInnerTicks(TradeIndicationSeries innerTicks) {
		this.innerTicks = innerTicks;
	}

	/**
	 * returns the associated innerCandles(TimeSeries&lt;Candle&gt;) as a Candle[] array
	 * @return the innerSeries
	 */
	public Candle[] getInnerCandles() {
		return ArrayUtils.asArray(innerCandles, Candle.class);
	}

	/**
	 * Sets the associated innerCandles(TimeSeries&lt;Candle&gt;) with a CandleSeries constructed using the given timeFrame(TimeFrame), innerSeries(Candle[]) and the associated instrumentSpecification(InstrumentSpecification)<br/>
	 * <strong>1.</strong> constructs a spec(SeriesSpecification) using the associated instrumentSpecification(InstrumentSpecification) and the given timeFrame(TimeFrame) to set its 
	 * associated instrumentSpecification(InstrumentSpecification) and timeFrame(TimeFrame)<br/>
	 * <strong>2.</strong> constructs a CandleSeries using that spec(SeriesSpecification) and the given innerSeries(Candle[]) and then sets the associated innerCandles(TimeSeries&lt;Candle&gt;)
	 * with it
	 * @param innerSeries
	 *            the innerSeries to set
	 */
	public void setInnerCandles(TimeFrame timeFrame, Candle[] innerSeries) {
		SeriesSpecification spec = new SeriesSpecification(this.getInstrumentSpecification(), timeFrame);
		this.innerCandles = new CandleSeries(spec, innerSeries);
	}
	/**
	 * returns whether the associated innerCandles(TimeSeries&lt;Candle&gt;) is not <code>null</code>
	 * @return
	 */
	public boolean hasInnerCandles() {
		return innerCandles != null;
	}
	/**
	 * returns the associated timeFrame(TimeFrame)
	 * @return
	 */
	public TimeFrame getTimeFrame() {
		return timeFrame;
	}
	/**
	 * sets the associated timeFrame(TimeFrame) with the given val(TimeFrame)
	 * @param val
	 */
	public void setTimeFrame(TimeFrame val) {
		timeFrame = val;
	}
	/**
	 * returns the associated count(int)
	 * @return
	 */
	public int getCount() {
		return count;
	}
	/**
	 * sets the associated count(int) with the given count(int)
	 * @param count
	 */
	public void setCount(int count) {
		this.count = count;
	}
	/**
	 * returns the associated WAP(double)
	 * @return
	 */
	public double getWAP() {
		return WAP;
	}
	/**
	 * sets the associated WAP(double) with the given WAP(double)
	 * @param WAP
	 */
	public void setWAP(double WAP) {
		this.WAP = WAP;
	}
	/**
	 * returns the associated hasGaps(boolean)
	 * @return
	 */
	public boolean isHasGaps() {
		return hasGaps;
	}
	/**
	 * sets the associated hasGaps(boolean) with the given hasGaps(boolean)
	 * @param hasGaps
	 */
	public void setHasGaps(boolean hasGaps) {
		this.hasGaps = hasGaps;
	}
}